﻿/* 
 * Rug.Cmd part of Rugland Console Framework
 * 
 * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
 * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * Copyright (C) 2008 Phill Tew. All rights reserved.
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Rug.Cmd.Colors;

namespace Rug.Cmd
{
    public class ConsoleFormatter
    {
        private StringBuilder m_Result = new StringBuilder();

		public int Length { get { return m_Result.Length; } }

		#region Clear

		public void Clear()
        {
            m_Result = m_Result.Remove(0, m_Result.Length); 
        }

		#endregion

		#region To String

		public override string ToString()
		{
			return m_Result.ToString();
		}

		#endregion

		#region Write and Write Line

		public void WriteLine()
		{
			m_Result.AppendFormat(Environment.NewLine);
		}

        public void Write(ConsoleColorExt color, string message)
        {
            if (color != ConsoleColorExt.Inhreit)
                m_Result.AppendFormat("<c:{0}>{1}</c>", (int)color, EscapeString(message));
            else
                m_Result.AppendFormat("{0}", EscapeString(message));
        }

        public void WriteLine(ConsoleColorExt color, string message)
        {
            if (color != ConsoleColorExt.Inhreit)
                m_Result.AppendFormat("<c:{0}>{1}</c>{2}", (int)color, EscapeString(message), Environment.NewLine);
            else
                m_Result.AppendFormat("{0}{1}" , EscapeString(message), Environment.NewLine);
        }

		public void Write(ConsoleColorExt color, string format, params object[] args)
        {
            if (color != ConsoleColorExt.Inhreit)
                m_Result.AppendFormat("<c:{0}>{1}</c>{2}", (int)color, EscapeString(string.Format(format, args)), Environment.NewLine);
            else
                m_Result.Append(EscapeString(string.Format(format, args)));
        }

        public void Write(ConsoleThemeColor color, string message)
        {
            m_Result.AppendFormat("<t:{0}>{1}</t>", (int)color, EscapeString(message));
        }

        public void WriteLine(ConsoleThemeColor color, string message)
        {
            m_Result.AppendFormat("<t:{0}>{1}</t>{2}", (int)color, EscapeString(message), Environment.NewLine);
        }

        public void Write(ConsoleThemeColor color, string format, params object[] args)
        {         
            m_Result.AppendFormat("<t:{0}>{1}</t>{2}", (int)color, EscapeString(string.Format(format, args)), Environment.NewLine);         
        }
		
		#endregion

		#region Static Members

		#region Escape / Unescape String

		public static string EscapeString(string str)
		{
			return str.Replace("<", "&lt;").Replace(">", "&gt;");
		}

		public static string UnescapeString(string str)
		{
			return str.Replace("&lt;", "<").Replace("&gt;", ">");
		}

		#endregion

		#region Interpreted Console Write

		public static readonly Regex FormatRegex =
			new Regex(@"(?<Tag>\x3c(?<Inner>c:\s*(\d+))\x3e)|(?<EndTag>\x3c\x2fc:\s*(\d+)\x3e)|(?<EndTag>\x3c\x2fc\x3e)|" +
					  @"(?<Tag>\x3c(?<Inner>t:\s*(\d+))\x3e)|(?<EndTag>\x3c\x2ft:\s*(\d+)\x3e)|(?<EndTag>\x3c\x2ft\x3e)|(?<Text>[^\x3c\x3e]+)",
                RegexOptions.Compiled |
                RegexOptions.IgnoreCase |
                RegexOptions.ExplicitCapture);


        public static readonly Regex FormatStripperRegex =
			new Regex(@"(?<Tag>\x3cc:\s*(\d+)\x3e)|(?<EndTag>\x3c\x2fc:\s*(\d+)\x3e)|(?<EndTag>\x3c\x2fc\x3e)|" +
					  @"(?<Tag>\x3ct:\s*(\d+)\x3e)|(?<EndTag>\x3c\x2ft:\s*(\d+)\x3e)|(?<EndTag>\x3c\x2ft\x3e)",
                RegexOptions.Compiled |
                RegexOptions.IgnoreCase |
                RegexOptions.ExplicitCapture);

		#region Split lines 
		
		public static List<string> SplitLinebreaks(string buffer)
        {
            string buff = buffer;
            List<string> lines = new List<string>(); 

            while (buff.Length > 0)
            {
                int index = buff.IndexOfAny(new char[] { '\r', '\n' });

                if (index >= 0)
                {
                    if (buff[index] == '\r' && buff.Length > index + 1 && buff[index + 1] == '\n')
                    {
                        lines.Add(buff.Substring(0, index));
                        lines.Add(buff.Substring(index, 2));
                        buff = buff.Substring(index + 2);
                    }
                    else
                    {
                        lines.Add(buff.Substring(0, index));
                        lines.Add(buff.Substring(index, 1));
                        buff = buff.Substring(index + 1);
                    }
                }
                else
                {
                    lines.Add(buff);
                    buff = "";
                }
            }

            return lines;
        }

		#endregion
		
		public static ConsoleColorExt ParseColour(string tagInner, ConsoleColorTheme theme)
        {
            string trimmed = tagInner.Trim();
            int index = trimmed.IndexOf(':');

            if (index == 1 && trimmed[0] == 'c')
            {
                string colourString = trimmed.Substring(2);

                int intValue = 0;

                if (int.TryParse(colourString, out intValue))
                {
                    return (ConsoleColorExt)intValue;
                }
                else
                {
                    try
                    {
                        return (ConsoleColorExt)Enum.Parse(typeof(ConsoleColorExt), colourString, true);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(string.Format(Strings.ConsoleInterpreter_ParseColour_Error, trimmed), ex);
                    }
                }
            }
            else if (index == 1 && trimmed[0] == 't')
            {
                string colourString = trimmed.Substring(2);

                int intValue = 0;

                if (int.TryParse(colourString, out intValue))
                {
                    return theme[(ConsoleThemeColor)intValue];
                }
                else
                {
                    try
                    {
                        return theme[(ConsoleThemeColor)Enum.Parse(typeof(ConsoleThemeColor), colourString, true)];
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(string.Format(Strings.ConsoleInterpreter_ParseColour_Error, trimmed), ex);
                    }
                }
            }
            else
                throw new Exception(string.Format(Strings.ConsoleInterpreter_ParseColour_Error, trimmed));
        }

        private static string ReplaceLineEnds(string value)
        {
            return value.Replace("\n", @"\par" + "\r\n").Replace("\n\n", "\n").Replace("\r\r", "\r").Replace(@"{", @"\{").Replace(@"}", @"\}");
        }

		#region FormatAsRtf

		public static string FormatAsRtf(string buffer, ConsoleColorTheme theme)
        {
            StringBuilder output = new StringBuilder(); 

            Stack<ConsoleColorExt> stack = new Stack<ConsoleColorExt>();

            foreach (Match match in FormatRegex.Matches(buffer))
            {
                if (match.Groups["Tag"].Success)
                {
                    // start tag 
                    // parse the tags inner value
                    // e.g. c:# (where # is the name or ID of the colour to use (From ConsoleColourExt) 
                    // add the parsed colour to the stack 

                    ConsoleColorExt col = ParseColour(match.Groups["Inner"].Value, theme);

                    stack.Push(col);

                    output.Append(@"\cf" + ((int)col + 1).ToString() + @"\ulnone ");
                }
                else if (match.Groups["EndTag"].Success)
                {
                    // end tag 
                    // handle stack changes
					if (stack.Count >= 0)
					{
						output.Append(@"\cf" + ((int)stack.Pop() + 1).ToString() + @"\ulnone ");
					}
					else
					{ 
                        throw new Exception(string.Format(Strings.ConsoleInterpreter_UnexpectedEndTag, match.Index));
					}
                }
                else if (match.Groups["Text"].Success)
                {
                    string wholeMessage = ConsoleFormatter.UnescapeString(match.Value);
                    
					output.Append(ReplaceLineEnds(wholeMessage)); 
                }
            }

            return output.ToString(); 
        }

		#endregion

		#region Write Interpreted

		public static void WriteInterpreted(IConsole console, string buffer)
        {
			WriteInterpreted(console, buffer, 0, 0);
        }

		public static void WriteInterpreted(IConsole console, ConsoleColorExt colour, string buffer, int paddingLeft, int paddingRight)
		{
			ConsoleColorExt col = console.ForegroundColor;

			if (colour != ConsoleColorExt.Inhreit)
			{ 
				console.ForegroundColor = colour;
			}

			WriteInterpreted(console, buffer, paddingLeft, paddingRight);

			console.ForegroundColor = col;
		}

        public static void WriteInterpreted(IConsole console, string buffer, int paddingLeft, int paddingRight)
        {
            int maxWidth = console.BufferWidth - (paddingLeft + paddingRight);

            string prefix = new string(' ', paddingLeft);

            int lastIndex = 0;
            Stack<ConsoleColorExt> stack = new Stack<ConsoleColorExt>();

            foreach (Match match in FormatRegex.Matches(buffer))
            {
                if (match.Groups["Tag"].Success)
                {
                    // start tag 
                    // parse the tags inner value
                    // e.g. c:# (where # is the name or ID of the colour to use (From ConsoleColourExt) 
                    // add the parsed colour to the stack 

                    ConsoleColorExt col = ParseColour(match.Groups["Inner"].Value, console.Theme);

                    stack.Push(console.ForegroundColor);

                    console.ForegroundColor = col;

                }
                else if (match.Groups["EndTag"].Success)
                {
                    // end tag 
                    // handle stack changes
					if (stack.Count >= 0)
					{
						console.ForegroundColor = stack.Pop();
					}
					else
					{ 
                        throw new Exception(string.Format(Strings.ConsoleInterpreter_UnexpectedEndTag, match.Index));
					}
                }
                else if (match.Groups["Text"].Success)
                {
                    string wholeMessage = ConsoleFormatter.UnescapeString(match.Value);

                    List<string> lines = SplitLinebreaks(wholeMessage);

                    foreach (string line in lines)
                    {
                        string str = line;

                        if (str == "\n" || str == Environment.NewLine)
                        {
                            console.WriteLine(ConsoleVerbosity.Silent);
                            lastIndex = 0;
                        }
                        else
                        {

                            while (str.Length > 0)
                            {
                                if (lastIndex + str.Length > maxWidth)
                                {
									int lastWord = str.LastIndexOf(' ', maxWidth - lastIndex, maxWidth - lastIndex);

									if (lastWord <= 0)
									{
										lastWord = maxWidth - lastIndex; 
									}

									string toRender; 

									if (lastIndex > 0)
									{
										toRender = str.Substring(0, lastWord);
									}
									else 
									{
										toRender = prefix + str.Substring(0, lastWord); 
									}

									if (console.BufferWidth == lastIndex + toRender.Length)
									{
										console.Write(ConsoleVerbosity.Silent, toRender);
									}
									else
									{
										console.WriteLine(ConsoleVerbosity.Silent, toRender);
									}

									str = str.Substring(lastWord + 1);

                                    lastIndex = 0;
                                }
                                else
                                {
									string toRender = str; 

									if (lastIndex <= 0)
									{
										toRender = prefix + str;                                         
									}

									console.Write(ConsoleVerbosity.Silent, toRender);

                                    lastIndex += str.Length;

                                    str = "";
                                }
                            }
                        }
                    }                   
                }
            }
        }

		#endregion

		#region Write Interpreted Line

		public static void WriteInterpretedLine(IConsole console, string buffer)
        {
            WriteInterpreted(console, buffer + Environment.NewLine);
        }

		#endregion

		#region Build Tags
		
		private static string BuildTagOpenString(Stack<string> stack)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0, e = stack.Count; i < e; i++)
                sb.Append("<{" + i + "}>");

            return string.Format(sb.ToString(), stack.ToArray()); 
        }

        private static string BuildTagCloseString(Stack<string> stack)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0, e = stack.Count; i < e; i++)
                sb.Append("</c>");

            return sb.ToString(); 
        }

		#endregion

		#region Strip Formatting
		
		public static string StripFormat(string buffer)
        {
            return FormatStripperRegex.Replace(buffer, ""); 
        }
		
		#endregion

		#region Split Lines
		
		public static string[] SplitLines(string buffer, int maxWidth)
        {
            int lastIndex = 0;
            Stack<string> stack = new Stack<string>();
			List<string> outLines = new List<string>(); 

            StringBuilder sb = new StringBuilder(); 

            foreach (Match match in FormatRegex.Matches(buffer))
            {
                if (match.Groups["Tag"].Success)
                {
                    // start tag 
                    // parse the tags inner value
                    // e.g. c:# (where # is the name or ID of the colour to use (From ConsoleColourExt) 
                    // add the parsed colour to the stack 

                    stack.Push(match.Groups["Inner"].Value);
                    sb.Append(match.Value); 

                }
                else if (match.Groups["EndTag"].Success)
                {
                    // end tag 
                    // handle stack changes
					if (stack.Count >= 0)
					{
						stack.Pop();
					}
					else
					{ 
                        throw new Exception(string.Format(Strings.ConsoleInterpreter_UnexpectedEndTag, match.Index));
					}

                    sb.Append(match.Value); 
                }
                else if (match.Groups["Text"].Success)
                {
                    string wholeMessage = ConsoleFormatter.UnescapeString(match.Value);

                    List<string> lines = SplitLinebreaks(wholeMessage);

                    foreach (string line in lines)
                    {
                        string str = line;

                        if (line == "\n" || line == Environment.NewLine)
                        {
                            // close all tags  
                            sb.Append(BuildTagCloseString(stack));

                            outLines.Add(sb.ToString());
                            sb = new StringBuilder();

                            // open all active tags 
                            sb.Append(BuildTagOpenString(stack));

                            lastIndex = 0;
                        }
                        else 
                        {
                            while (str.Length > 0)
                            {
                                if (lastIndex + str.Length > maxWidth)
                                {
                                    if (lastIndex > 0)
                                    {
                                        sb.Append(ConsoleFormatter.EscapeString(str.Substring(0, maxWidth - lastIndex)));
                                        // close all tags 
                                        sb.Append(BuildTagCloseString(stack));

                                        outLines.Add(sb.ToString());

                                        sb = new StringBuilder();

                                        // open all active tags 
                                        sb.Append(BuildTagOpenString(stack));
                                    }
                                    else
                                    {
                                        sb.Append(ConsoleFormatter.EscapeString(str.Substring(0, maxWidth)));
                                        // close all tags  
                                        sb.Append(BuildTagCloseString(stack));

                                        outLines.Add(sb.ToString());
                                        sb = new StringBuilder();

                                        // open all active tags 
                                        sb.Append(BuildTagOpenString(stack));
                                    }

                                    str = str.Substring(maxWidth - lastIndex);

                                    lastIndex = 0;
                                }
                                else
                                {
                                    if (wholeMessage.EndsWith("\n") || wholeMessage.EndsWith(Environment.NewLine))
                                    {
                                        sb.Append(ConsoleFormatter.EscapeString(str));
                                        // close all tags  
                                        sb.Append(BuildTagCloseString(stack));

                                        outLines.Add(sb.ToString());
                                        sb = new StringBuilder();

                                        // open all active tags 
                                        sb.Append(BuildTagOpenString(stack));

                                        lastIndex = 0;
                                    }
                                    else
                                    {
                                        sb.Append(str);
                                        lastIndex += str.Length;
                                    }

                                    str = "";
                                }
                            }
                        }
                    }                   
                }
            }

            if (sb.Length > 0)
            {
                sb.Append(BuildTagCloseString(stack));
                outLines.Add(sb.ToString());
            }

            return outLines.ToArray(); 
        }

		#endregion

		#region Substring (with color persitance) 
		
		public static string Substring(string buffer, int index, int length)
        {
            Stack<string> stack = new Stack<string>();         
            StringBuilder sb = new StringBuilder();
            int currentCharCount = 0;
            int endIndex = index + length;
            bool midString = false; 

            foreach (Match match in FormatRegex.Matches(buffer))
            {
                if (match.Groups["Tag"].Success)
                {
                    stack.Push(match.Groups["Inner"].Value);

                    if (midString)
                        sb.Append(match.Value); 
                }
                else if (match.Groups["EndTag"].Success)
                {
                    // end tag 
                    // handle stack changes
                    if (stack.Count >= 0)
                        stack.Pop();
                    else
                        throw new Exception(string.Format(Strings.ConsoleInterpreter_UnexpectedEndTag, match.Index));

                    if (midString)
                        sb.Append(match.Value); 
                }
                else if (match.Groups["Text"].Success)
                {
                    string wholeMessage = ConsoleFormatter.UnescapeString(match.Value);

                    List<string> lines = SplitLinebreaks(wholeMessage);

                    foreach (string line in lines)
                    {
                        string str = line;
                        
                        if (currentCharCount >= endIndex) 
                            continue;

                        if (currentCharCount > index)
                        {
                            // the start of the substring has passed and the end is to come                            
                            int end = endIndex - currentCharCount;

                            if (end > str.Length)
                            {                             
                                sb.Append(ConsoleFormatter.EscapeString(str));
                                currentCharCount += str.Length;
                            }
                            else
                            {                             
                                sb.Append(ConsoleFormatter.EscapeString(str.Substring(0, end)));
                                sb.Append(BuildTagCloseString(stack));
                                return sb.ToString();
                            }
                        }
                        else if (currentCharCount + str.Length >= index)
                        {
                            // the substring will happen within this string
                            int start = index - currentCharCount; 
                            currentCharCount += start;
                            str = str.Substring(start);

                            int end = endIndex - currentCharCount;

                            if (end > str.Length)
                            {
                                sb.Append(BuildTagOpenString(stack));
                                sb.Append(ConsoleFormatter.EscapeString(str));
                                currentCharCount += str.Length;
                                midString = true; 
                            }
                            else
                            {
                                sb.Append(BuildTagOpenString(stack));
                                sb.Append(ConsoleFormatter.EscapeString(str.Substring(0, end)));
                                sb.Append(BuildTagCloseString(stack));
                                return sb.ToString(); 
                            }
                        }
                        else
                        {
                            currentCharCount += str.Length; 
                        }
                    }
                }
            }

            sb.Append(BuildTagCloseString(stack));
            
            return sb.ToString();
        }

		#endregion

		#endregion

		#region Format Methods
		
		public static string Format(ConsoleColorExt color, string message)
        {
            if (color != ConsoleColorExt.Inhreit)
                return string.Format("<c:{0}>{1}</c>", (int)color, EscapeString(message));
            else
                return string.Format("{0}", EscapeString(message));
        }

        public static string FormatLine(ConsoleColorExt color, string message)
        {
            if (color != ConsoleColorExt.Inhreit)
                return string.Format("<c:{0}>{1}</c>{2}", (int)color, EscapeString(message), Environment.NewLine);
            else
                return string.Format("{0}{1}", EscapeString(message), Environment.NewLine);
        }

        public static string Format(ConsoleColorExt color, string format, params object[] args)
        {
            if (color != ConsoleColorExt.Inhreit)
                return string.Format("<c:{0}>{1}</c>{2}", (int)color, EscapeString(string.Format(format, args)), Environment.NewLine);
            else
                return EscapeString(string.Format(format, args));
		}

		#endregion

		#endregion
	}
}
